/*
PearReview - The peer review assistant.
Copyright (C) 2009  Dimitrij Pankratz, Anton Musichin
http://www.pearreview.com, info@pearreview.com

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

package com.pearreview.ui;

import java.awt.*;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;

import javax.swing.*;
import javax.swing.border.EmptyBorder;
import javax.swing.event.*;

import java.util.*;
import java.util.List;
import java.util.regex.*;

/**
 * @author Dimitrij Pankratz, Anton Musichin
 * @version 1.0
 *
 */
public class CompletableJTextField extends JTextField {
	private static final long serialVersionUID = 8638109799676116487L;
	
	private final static int POPUP_HEIGHT = 150;

	private Completer completer;
	private JList completionList;
	private DefaultListModel completionListModel;
	
	private JScrollPane listScroller;
	
	//private JWindow listWindow;
	private JPopupMenu listWindow;
	
	public CompletableJTextField () {
		super ();
		initComponents();
	}
	
	public CompletableJTextField(String string) {
		super (string);
		initComponents();
	}
	
	public void initComponents() {
		setFont(new Font("Tahoma", Font.PLAIN, 12));
		setBorder(BorderFactory.createMatteBorder(
				1, 1, 1, 1, new Color(0x828790)));
		
		completer = new Completer();
		completionListModel = new DefaultListModel();
		completionList = new JList(completionListModel);
		completionList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
		completionList.setBorder(null);
		completionList.setSelectionBackground(new Color(0xbfd72b));
		
		listScroller = new JScrollPane (completionList);
		//listScroller.setHorizontalScrollBarPolicy(
		//		ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
		listScroller.setBorder(BorderFactory.createMatteBorder(
				1, 1, 1, 1, new Color(0x828790)));
		completionList.addMouseMotionListener(new MouseMotionAdapter() {
			@Override
			public void mouseMoved(MouseEvent e) {
				int index = completionList.locationToIndex(e.getPoint());
				if(index >= 0)
					completionList.setSelectedIndex(index);
			}
		});
		completionList.addMouseListener(new MouseAdapter() {
			@Override
			public void mouseClicked(MouseEvent e) {
				selected();
			}
		});
		completionList.addKeyListener(new KeyAdapter() {
			@Override
			public void keyPressed(KeyEvent e) {
				if(e.getKeyCode() == KeyEvent.VK_ENTER) {
					selected();
				} else if(e.getKeyCode() == KeyEvent.VK_ESCAPE) {
					completer.hidePopup();
				}
			}
		});
		completionList.addFocusListener(new FocusAdapter() {
			@Override
			public void focusLost(FocusEvent e) {
				if(listWindow.isVisible())
					CompletableJTextField.this.requestFocusInWindow();
			}
		});
		
		listWindow = new JPopupMenu();
		listWindow.setBorder(new EmptyBorder(0, 0, 0, 0));
		listWindow.setFocusable(false);
		listWindow.add(listScroller);
		
		addKeyListener(new KeyAdapter() {
			@Override
			public void keyPressed(KeyEvent e) {
				if(e.getKeyCode() == KeyEvent.VK_DOWN) {
					int selectedIndex = completionList.getSelectedIndex();
					completer.buildAndShowPopup();
					completionList.requestFocusInWindow();
					if(completionListModel.getSize() > 0) {
						if(selectedIndex < 0)
							selectedIndex = 0;
						
						selectedIndex = Math.min(selectedIndex + 1, 
								completionListModel.getSize() - 1);
						
						completionList.setSelectedIndex(selectedIndex);
					}
				} else if(e.getKeyCode() == KeyEvent.VK_UP) {
					completer.buildAndShowPopup();
				} else if(e.getKeyCode() == KeyEvent.VK_ENTER) {
					completer.hidePopup();
				} else if(e.getKeyCode() == KeyEvent.VK_ESCAPE) {
					completer.hidePopup();
				}
			}
		});
		addFocusListener(new FocusAdapter() {
			@Override
			public void focusLost(FocusEvent e) {
				if(!listWindow.isVisible())
					completer.hidePopup();
			}
		});
		
		//--- fading effect ---
		FadingManager.getInstance().registerComponent(this);
		
		//--- contextMenu ---
		CutCopyPasteManager.getInstance().registerJTextComponent(this);
	}

	/**
 	* 
 	* @param s
 	*/
	public void addCompletion (String s) {
		completer.addCompletion (s); 
	}

	/**
 	* 
 	* @param s
 	*/
	public void removeCompletion (String s) {
		completer.removeCompletion (s); 
	}

	/**
 	* 
 	* @param s
 	*/
	public void clearCompletions (String s ) {
		completer.clearCompletions (); 
	}
	
	public List<String> getComplitionList() {
		return completer.getComplitionList();
	}
	
	/**
 	* 
 	* @param completions
 	*/
	public void setCompletionList(java.util.List<String> completions) {
		this.completer.completions = completions;
	}
	
	private void selected() {
		String temp = null;
		if (completionList.getSelectedValue() != null) {
			temp = (String) completionList.getSelectedValue();
		
			final String completionString = temp;
			Thread worker = new Thread() {
					public void run() {
						setText (completionString);
					}
				};
			SwingUtilities.invokeLater (worker);
		}
		
		completer.hidePopup();
		
		requestFocusInWindow();
	}

	/**
 	* Auto completion for the text field.
 	*/
	class Completer implements DocumentListener {
		private Pattern pattern;
		private java.util.List<String> completions;
		public Completer() {
			completions = new ArrayList<String>();
			
			getDocument().addDocumentListener (this);
		}
		
		public void addCompletion (String s) {
			if (!completions.contains(s)) {
				completions.add (s);
				buildAndShowPopup();
			}
		}
		
		public void removeCompletion (String s) {
			completions.remove (s);
			buildAndShowPopup();		
		}
		
		public void clearCompletions () {
			completions.clear();
			buildPopup();
			hidePopup();
		}
		
		public void setCompletionList(ArrayList<String> completions) {
			this.completions = completions;
		}
		
		public List<String> getComplitionList() {
			return completions;
		}
		
		private void buildPopup() {
			try {
				completionListModel.clear();
				Iterator<String> it = completions.iterator();
				pattern = Pattern.compile(
						".*" + getText().replace("*", ".*") + ".+", 
						Pattern.CASE_INSENSITIVE);
				while (it.hasNext()) {
					// check if match
					String completion = (String) it.next();
					Matcher matcher = pattern.matcher(completion);
					if (matcher.matches()) {
						completionListModel.add (completionListModel.getSize(),
								 completion);
					}
				}
			} catch (Exception ignore) {}
		}
		
		private void showPopup() {
			if (completionListModel.getSize() == 0) {
				hidePopup();
				return;
			}
			
			if(listWindow.isVisible())
				return;
			//java.awt.Point los = getLocationOnScreen();
			int popX = 0;
			int popY = getHeight();
			

			
			listWindow.pack();
			if (CompletableJTextField.this.isShowing() && 
					CompletableJTextField.this.hasFocus()) {
				int width =CompletableJTextField.this.getSize().width;
				Dimension dim = new Dimension(width, POPUP_HEIGHT);
				listScroller.setPreferredSize(dim);
				listWindow.show(CompletableJTextField.this, popX, popY);
			}
		}
		
		public void hidePopup() {
			listWindow.setVisible(false);
			listWindow.setFocusable(false);
		}
		
		public void buildAndShowPopup() {
			if (getText().length() < 1)
				return;
			buildPopup();
			showPopup();
		}
		
		private void textChanged() {
			if(getText() != null && !getText().equals(""))
				buildAndShowPopup(); 
			else
				hidePopup(); 
		}
		
		@Override
		public void insertUpdate (DocumentEvent e) {
			textChanged();
		}
		@Override
		public void removeUpdate (DocumentEvent e) {
			textChanged();
		}
		
		@Override
		public void changedUpdate (DocumentEvent e) {
			textChanged();
		}
	}
}